﻿//
// Created by LiuYou on 2021/4/9.
//

#include <stdio.h>
#include <string.h>

/**
 * @brief 标准输入输出重定向
 * 对数据的输入/输出是通过终端设备来完成的，看似与文件毫无瓜葛，但是实际上，对于终端设备，系统会自动打开3个标准文件:
 *      * 标准输入文件
 *      * 标准输出文件
 *      * 标准错误文件
 * 相应的，系统定义了3个特别的文件指针常数:
 *      * stdin 指向标准输入文件
 *      * stdout 指向标准输出文件
 *      * stderr 指向标准错误文件
 * 这3个文件都以标准终端设备作为输入/输出对象。
 * 默认情况下，标准输入设备是键盘，标准输出设备是屏幕。
 */
void standardInputOutputRedirection() {

}

/**
 * @brief 文件的自由随机读写
 *
 * C语言支持文件的随机访问，允许在文件随机定位并在文件的任何位置直接读写数据。
 * 为了实现文件的定位，在每一个打开的文件中，都有一个文件位置指针(File Location Pointer)，也称为文件位置标记，
 * 用来指向当前读写文件位置。
 * C语言提供了如下函数来定位文件位置指针:
 *
 *      int fseek(FILE* fp, long offset, int fromwhere);
 *          将fp的文件位置从fromwhere开始移动offset个字节，指示下一个要读取的数据的位置。
 *          offset: 一个偏移量，让文件位置指针要跳过多少个字节。offset为正时，文件位置指针向 前 移动，为负时，文件位置指针向 后 移动。
 *              ANSI C要求位移量offset是长整型数据(常量数据后要加L)，这样当文件的长度大于64k不至于出问题。
 *          fromwhere用于确定偏移量计算的起始位置。它的取值有3种:
 *              SEEK_SET或0 表示文件开始处
 *              SEEK_CUR或1 表示文件当前位置
 *              SEEK_END或2 表示文件结尾处
 *          通过fromwhere和offset的值，可使位置指针移动到文件的任意位置，从而实现文件的随机读取。
 *          函数调用成功返回值为0，失败返回非0值。
 *
 *      void rewind(FILE* fp);
 *          将文件位置指针指向文件首字节，即重置文件位置指针到文件首部。
 *
 *      long ftell(FILE* fp);
 *          用来读取当前文件位置指针
 *          函数调用成功返回文件的当前读写位置，否则返回-1L。而且用相对于文件起始位置的字节偏移量来表示返回的当前文件位置指针。
 *
 *
 * fflush()
 *      int fflush(FILE* fp);
 *      无条件把缓冲区中的所有数据写入物理设备。这样，程序员可自己决定在何时清除缓冲区中的数据，以确保输出缓冲区中的内容写入文件。
 *
 */
void randomAccess() {
    FILE* filePtr = fopen("Test", "wb");
    // length=21
    char* ptrStr = "I love C/C++ forever!";
    char* ptr = ptrStr;

    //region 1
    // region 1有瑕疵，在后面的代码中进行了完善
//    while (*ptr != '\0') {
//        fwrite(ptr, 2, 1, filePtr);
////        ++ptr;
//        // 如果加2的话，因为是21个字节长度，最后的文件还是写入的时候有一些瑕疵！！！！
//        ptr += 2;
//    }
    //endregion

    int strLength = 0;
    // 这里大错误之前是使用ptr作为判断条件，我的天太傻了。真的硬看看不出来哪里有错
    // 调试都要调试好几次，不多调试发现不了！！！！
    // 又进行了一次调试发现: 不就是要使用ptr作为判断条件，出现死循环的真正原因是没有设置循环条件！
    while (*ptr != '\0') {
        ++strLength;
        ++ptr;
    }
    ptr = ptrStr;
    int transferSize = 2;
    int transferCount = strLength / transferSize;
    int remainder = strLength % transferSize;
    for (int i = 0; i < transferCount; ++i) {
//        printf("写入了%d个数据块\n", fwrite(ptr, transferSize, 1, filePtr));
        fwrite(ptr, transferSize, 1, filePtr);
        printf("写入文件%f%%...\n", (double) transferSize * 1 * (i + 1) / strLength * 100);
        ptr += transferSize;
    }
    fwrite(ptr, remainder, 1, filePtr);
    printf("写入文件100%%, 写入文件成功！\n");
    // 2
    // 10 2
    // 1 1

    fclose(filePtr);

    // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    // 然后是从文件中读取
    FILE* fp = fopen("Test", "r");
    char str[100] = "";
    // 不需要一块空间用来接收字符，直接使用一个指针就可以了！！！！！
    //    char buffer[1] = "";
    char* buffer = str;
    // TODO 问题在这里！！！！！！！！！！！！！！！！！
    // TODO 解决: 其实没有必要使用memset()函数
    // TODO 注意: 2021年4月12日16:49:07 此时我将下面这两行代码又加上了，编译运行发现并没有问题！
    memset(str, 0, sizeof(str));
    memset(buffer, 0, sizeof(str));
    int i = 0;

//    while (i < 21) {
    while (feof(fp) == 0) {
//    while (!feof(fp)) {
        // TODO 注意: freed()函数的文件位置指针是自动向后移动的！！！！！
        fread(buffer, sizeof(char), 1, fp);
        // SEEK_SET就不会出错
//        printf("%d", fseek(fp, (i + 1) * sizeof(char), SEEK_SET));

        // +++++++++++++++++++++++++++++++++++++++
        // TODO 注意: 使用fseek()函数时使用SEEK_CUR会有BUG！！不要使用SEEK_CUR，使用SEEK_SET！！！！
        // TODO 不知道为什么SEEK_CUR会错？？？？？
        // TODO 问题解决了 2021年4月12日22:10:51 是正前移，负才后移。并不是BUG！！！！
//        printf("%d", fseek(fp, sizeof(char), SEEK_CUR));
        // 不行就是有问题，有BUG。
//        printf("%d", fseek(fp, (long) sizeof(char), SEEK_CUR));
        // 就是不行！！！！
//        printf("%d", fseek(fp, (long) sizeof(char), ftell(fp)));
        // +++++++++++++++++++++++++++++++++++++++

//        str[i] = *buffer;
//        printf(" %c\n", str[i]);
//        ++i;

        printf(" %c\n", *buffer);
        ++buffer;
    }
    printf("文件中的内容为: %s\n", str);
    fclose(fp);
    // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    // TODO 如何判断已经到文件结尾了？
//    fseek(filePtr, 1L, SEEK_CUR);

}

/**
 * @brief 文件操作
 * C语言文件有两种类型:
 *      * 文本文件(也称为ASCII码文件)(*.txt文件)
 *      * 二进制文件
 *
 * 文件的打开和关闭
 *      打开
 *          FILE* fopen(const char* filename, const char* mode);
 *          fopen()函数返回值是一个文件指(FILE*)。FILE是一个结构体类型。
 *          filename: filename表示文件名，可包含路径和文件名两部分。
 *          mode: mode表文件打开方式。
 *              "r" : only read
 *              "w" : only write(已存在的文件将会被覆盖)
 *              "a" : 以只写的方式打开文件，位置指针移到文件末尾，向文件尾部添加数据
 *              "+" : 与上面字符串组合，表示以读写方式打开文本文件。即可以读也可以写
 *              "b" : 与上面字符串组合，表示打开二进制文件
 *
 *      关闭
 *          int fclose(FILE* fp);
 *          fclose()函数返回一个整数。如果文件正常关闭返回0值，否则返回一个非0值。
 *
 * 文件读写函数:
 *      * 按字符读写
 *      * 按数据块读写
 *      * 按格式读写
 *
 * 按字符读写
 *      fgetc()
 *          int fgetc(FILE* fp);
 *          用来从一个以只读或读写方式打开的文件上读字符。
 *          该函数是从fp所指向的文件中读取一个字符，并将位置指针指向下一个字符。
 *          若读取成功，则返回该字符，若读到文件末尾，则返回EOF(EOF是一个宏符号常量，在stdio.h中定义为-1)。
 *          所以文件的末尾是EOF宏符号常量。重要！！！！
 *
 *      fputc()
 *          int fputc(int c, FILE* fp);
 *          用来将一个字符写到一个文件上。
 *          c: c是要输入到文件中的字符，尽管是定义为int型。
 *          fp: 写入fp所指向的文件。
 *          函数执行成功返回字符c，若写入错误，返回EOF。
 *      我觉得使用字符读写不多。
 *
 * 函数: feof()
 *          int feof(FILE* fp);
 *          判断是否读到文件末尾
 *          当文件位置位置指针指向文件结束符(End of file Indicator)时，返回非0值，
 *          如果没有读取到文件末尾返回0值。
 *
 * 读写字符串
 *      char* fgets(char* s, int n, FILE* fp);
 *      从文件中读取字符串
 *      从文件中读入n-1个字符存入s。
 *
 *      int fputs(const char* s, FILE* fp);
 *      将字符串写入文件中
 *
 *      我觉得使用也不多。
 *
 * 按格式读写文件
 *      int fscanf(FILE* fp, const char* format, ...);
 *      int fprintf(FILE* fp, const char* format, ...);
 *      我觉得这个不使用。
 *
 * 按数据块读写文件
 *      unsigned int fread(void* buffer, unsigned int size, unsigned int count, FILE* fp);
 *          从fp所指向的文件中读取数据块并存储到buffer指向的内存中。
 *          buffer: 待读入数据块的起始地址。
 *          size: 每个数据块的大小(待读入的每个数据块的字节数)。
 *          count: 最多允许读取的数据块个数(每个数据块size个字节)。
 *          函数的返回值是实际读到的数据块个数。
 *
 *      unsigned int fwrite(const void* buffer, unsigned int size, unsigned int count, FILE* fp);
 *          将buffer指向的内存中的数据块写入fp所指向的文件。
 *          buffer: 待输出数据块的起始地址。
 *          size: 每个数据块的大小(待输出的每个数据块的字节数)。
 *          count: 最多允许写入的数据块个数(每个数据块size个字节)。
 *          函数的返回值是实际写入的数据块个数。
 *
 *      块数据的读写是我们不再局限于一次只读取一个字符、一个字符串。它允许用户指定想要读写的内存块大小，最小为1个字节，最大为整个文件。
 *      TODO 注意: 我觉得这个非常非常非常重要的！！！！
 *      这两个函数通常用于二进制文件的输入和输出！！！！
 *
 * memset
 *      void* memset(void* buffer, int ch, unsigned int count);
 *      把ch赋值给buffer指向的数组的前count个字节处。常用来把某个内存区域初始化为0值。
 *      函数返回值为buffer指针(void*类型)。
 *
 * void*
 *      可以用其指代任何类型的指针。但不能用void指针直接进行操作；只能转换成对应类型指针后，才能操作。
 *      因其可以指代任何类型的指针。所以将void指针作为函数参数时，表示接受任意类型的指针参数。
 *
 */
void fileOperation() {
    // TODO 注意: C/C++没有像Java那样可以获取文件路径的方法。
    // TODO 2021年4月13日22:42:46现在进行解释。回答是有的
    //      C语言的预定义宏 __FILE__ 是该文件所在计算机中的绝对路径！！！！
    FILE* filePtr = fopen("FileTest", "w");

    char strArray[100];
    memset(strArray, 0, sizeof(strArray));
    char* ptrStr = "I love C/C++ forever!";
    fwrite(ptrStr, 2, 3, filePtr);
    fwrite(ptrStr, 2, 3, filePtr);

    fclose(filePtr);

    fopen("FileTest", "r");
    fread(strArray, 8, 2, filePtr);
    fclose(filePtr);
    printf("%s", strArray);


}

int main() {
//    fileOperation();

    randomAccess();

    return 0;
}